#include "Compresor.h"

Compresor::Compresor() {}

Compresor::~Compresor() {}

bool Compresor::fueraRango(long x1, long y1,long alto_original, long ancho_original){
	if (alto_original < y1 || ancho_original < x1 )
		return true;
	return false;
}

void Compresor::media(long x0, long y0, long longitud, Bitmap * imagen,long &mR,long &mG,long &mB){
    mR = 0;
    mG = 0;
    mB = 0;
	for (int i = x0; i < (x0 + longitud); i++)
		for (int j = y0; j < (y0 + longitud); j++){
			mR += (long)R(imagen->getColor(i,j));
			mG += (long)G(imagen->getColor(i,j));
			mB += (long)B(imagen->getColor(i,j));
        }
    mR = (long)(mR/(longitud*longitud));	
    mG = (long)(mG/(longitud*longitud));	
    mB = (long)(mB/(longitud*longitud));	
}

void Compresor::desviacionstd(long x0, long y0, long longitud, Bitmap * imagen,double &dR,double &dG,double &dB){
	dR = 0;
	dG = 0;
	dB = 0;
    long mR = 0, mG = 0, mB = 0;    	
	media(x0,y0,longitud,imagen,mR,mG,mB);
	for (int i = x0; i < (x0 + longitud); i++){
			for (int j = y0; j < (y0 + longitud); j++){
				long rojo = (long)R(imagen->getColor(i,j))-mR;
				dR += rojo * rojo;
				long verde = (long)G(imagen->getColor(i,j))-mG;
				dG += verde * verde;
				long azul = (long)B(imagen->getColor(i,j))-mB;
				dB += azul * azul;
            }
	}
	dR = (double) sqrt(dR);
	dG = (double) sqrt(dG);
	dB = (double) sqrt(dB);	
}

bool Compresor::homogenea(long x0, long y0,long x1,long y1,Bitmap * imagen,long umbral){
	int longitud = x1 -x0 + 1;
	double dR,dG,dB;
	desviacionstd(x0,y0,longitud,imagen,dR,dG,dB);
	if ((dR<= umbral) && (dG<= umbral) && (dB<= umbral))
        return true;
	return false;
}

bool Compresor::esCuadrada(int alto,int ancho){
	if (alto == ancho)
		return true;
	return false;
}

bool Compresor::potenciaDos(int alto){
    double aux1 = log10(alto)/log10(2);
	int aux2 = aux1;
	if ((aux1-aux2) == 0)
		return true;
	return false;
}

int Compresor::potCercana(int alto,int ancho){
	if (alto > ancho)
		return ceil(log10(alto)/log10(2));
	else
		return ceil(log10(ancho)/log10(2));
}

void Compresor::completar(Bitmap *& imagen,int alto,int ancho){
    double dos = 2;
	int potencia = potCercana(alto,ancho);
	Bitmap * nuevo = new Bitmap((long)pow(dos,potencia),(long)pow(dos,potencia));
	for (int x = 0; x<=ancho; x++)
		for (int y = 0; y<=alto; y++)
			nuevo->setColor(x,y,imagen->getColor(x,y));
	delete imagen;
	imagen = nuevo;
	nuevo = NULL;
}

void Compresor::pintar(int x0,int y0,int x1,int y1,long color,Bitmap * &descomprimido,long ancho_original,long alto_original){
	int x = x0;
	int y = y0;
	while( (x <= x1) && (x <= ancho_original)){
		while( (y <= y1) && (y <= alto_original) ){
			descomprimido->setColor(x,y,color);
			y++;
		}
	y = y0;
	x++;
	}
}
void Compresor::cargar(long x0, long y0, long x1, long y1,long alto_original, long ancho_original,Bitmap * imagen,QuadTree *& quad,long umbral){
	if (fueraRango(x0,y0,alto_original,ancho_original))
		quad->agregarDatoHoja(imagen->getColor(x0,y0));
	else
		if (homogenea(x0,y0,x1,y1,imagen,umbral)){
            double dos = 2;
            long mR = 0,mG = 0,mB = 0;
            media(x0,y0,x1-x0+1,imagen,mR,mG,mB);
			int m=mR+mG*(long)pow(dos,8)+mB*(long)pow(dos,16);
			quad->agregarDatoHoja(m);
		}
		else{
			int mitadx = (x1+x0)/2;
			int mitady = (y1+y0)/2;
			quad->agregarDato();
			cargar(x0,y0,mitadx,mitady,alto_original,ancho_original,imagen,quad,umbral);//1er cuadrante
			cargar(mitadx+1,y0,x1,mitady,alto_original,ancho_original,imagen,quad,umbral);//2do cuadrante
			cargar(x0,mitady+1,mitadx,y1,alto_original,ancho_original,imagen,quad,umbral);//3er cuadrante
			cargar(mitadx+1,mitady+1,x1,y1,alto_original,ancho_original,imagen,quad,umbral);//4to cuadrante
		}
}

void Compresor::guardar(QuadTree * quad,int longitud,int alto_original,int ancho_original, const char * ruta_comprimido){
	ofstream salida(ruta_comprimido,std::ios::binary);
	salida.write((char *)&alto_original,sizeof(int));
	salida.write((char *)&ancho_original,sizeof(int));
	salida.write((char *)&longitud,sizeof(int));
	quad->guardar(salida);
	salida.close();
}

void Compresor::Comprimir(int tipo, const char * ruta_imagen, const char * ruta_destino, int umbral) {
	FILE * archivo = fopen(ruta_imagen,"rb");
	QuadTree * quad;
	if (archivo){
		if (tipo == VECTOR)
			quad = new QuadTreeVector();
		else
			if (tipo == HOJAS)
				quad = new QuadTreeHojas();
			else
			    if (tipo == HOJASC)  
			        quad = new QuadTreeHojasComp(); 
		Bitmap * imagen = new Bitmap(archivo);
		long alto_original = imagen->getHeight();
		long ancho_original = imagen->getWidth();
		if (!esCuadrada(alto_original,ancho_original) || !potenciaDos(alto_original))
			completar(imagen,alto_original-1,ancho_original-1);
		cargar(0,0,imagen->getWidth()-1,imagen->getHeight()-1,alto_original-1,ancho_original-1,imagen,quad,umbral);
		guardar(quad,imagen->getWidth(),alto_original,ancho_original,ruta_destino);
		fclose(archivo);
	}
}

void Compresor::recuperar(QuadTree * &recuperado,long & alto_original,long & ancho_original,long &longitud, const char * ruta_a_descomprimir) {
	int tipo = 0;
	ifstream entrada(ruta_a_descomprimir,ios::binary);
	entrada.read((char *)&alto_original,sizeof(int));
	entrada.read((char *)&ancho_original,sizeof(int));
	entrada.read((char *)&longitud,sizeof(int));
	entrada.read((char *)&tipo,sizeof(int));
	if (tipo == VECTOR)
		recuperado = new QuadTreeVector();
	else
		if (tipo == HOJAS)
			recuperado = new QuadTreeHojas();
		else
		  if (tipo == HOJASC)
		      recuperado = new QuadTreeHojasComp();
	recuperado->recuperar(entrada);
	entrada.close();
}

void Compresor::descomprimir(QuadTree * recuperado,long x0,long y0,long x1,long y1,Bitmap *& descomprimido,long ancho_original,long alto_original){
	if (!fueraRango(x0,y0,alto_original,ancho_original)){
		if (recuperado->esHoja()){
			pintar(x0,y0,x1,y1,recuperado->getDato(),descomprimido,ancho_original,alto_original);
		}
		else{
			int mitadx = (x1+x0)/2;
			int mitady = (y1+y0)/2;
			recuperado->avanzar(0);
			descomprimir(recuperado,x0,y0,mitadx,mitady,descomprimido,ancho_original,alto_original);
			recuperado->retroceder();
			recuperado->avanzar(1);
			descomprimir(recuperado,mitadx+1,y0,x1,mitady,descomprimido,ancho_original,alto_original);
			recuperado->retroceder();
			recuperado->avanzar(2);
			descomprimir(recuperado,x0,mitady+1,mitadx,y1,descomprimido,ancho_original,alto_original);
			recuperado->retroceder();
			recuperado->avanzar(3);
			descomprimir(recuperado,mitadx+1,mitady+1,x1,y1,descomprimido,ancho_original,alto_original);
			recuperado->retroceder();
		}
	}
}

void Compresor::reconstruir(QuadTree *& recuperado, Bitmap * &descomprimido,long alto,long ancho,long alto_original,long ancho_original,const char * ruta_descomprimido){
	descomprimido = new Bitmap(ancho_original,alto_original);
	recuperado->irRaiz();
	descomprimir(recuperado,0,0,ancho-1,alto-1,descomprimido,ancho_original-1,alto_original-1);
	FILE * archdes = fopen(ruta_descomprimido,"wb");
	if (archdes) {
		descomprimido->save(archdes);
		fclose(archdes);
	}
}


void Compresor::Descomprimir(const char * ruta_origen,const char * ruta_destino){
	long alto_original = 0, ancho_original = 0, longitud = 0;
	QuadTree * quad_recuperado;
	recuperar(quad_recuperado,alto_original,ancho_original,longitud,ruta_origen);
	Bitmap * descomprimido;
	reconstruir(quad_recuperado,descomprimido,longitud,longitud,alto_original,ancho_original,ruta_destino);
}
